package org.e_gold.service.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.log4j.Logger;
import org.e_gold.dao.CookieDAO;
import org.e_gold.dao.EventDAO;
import org.e_gold.dao.SettledContractDAO;
import org.e_gold.factory.AppProperties;
import org.e_gold.factory.Manager;
import org.e_gold.factory.export.MonitorThread;
import org.e_gold.model.Contract;
import org.e_gold.model.Event;
import org.e_gold.model.SettledContract;
import org.e_gold.service.AIService;
import org.e_gold.service.BrowserService;
import org.e_gold.service.ConfigService;
import org.e_gold.service.ContractService;
import org.e_gold.service.EventService;
import org.e_gold.service.SettledContractService;
import org.e_gold.service.UserAccountService;
import org.e_gold.util.Bet365;
import org.e_gold.util.CalcUtil;
import org.e_gold.util.Constant;
import org.e_gold.util.LogUtil;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BrowserServiceImpl extends _NavigatorService implements BrowserService, Constant, Bet365 {

	private static final Logger logger = Logger.getLogger(BrowserServiceImpl.class);

	private static final int SLEEP_PER_TIMES = 5;
	private static int waitingTimes = SLEEP_PER_TIMES;

	@Autowired
	private CookieDAO cookieDAO;

	@Autowired
	private EventService eventService;

	@Autowired
	private ContractService contractService;

	@Autowired
	private AIService aiService;

	@Autowired
	private SettledContractDAO settledContractDAO;

	@Autowired
	private SettledContractService settledContractService;

	@Autowired
	private EventDAO eventDAO;

	@Autowired
	private UserAccountService userAccountService;

	@Autowired
	private MonitorThread monitor;

	@Autowired
	private AppProperties appProperties;

	@Autowired
	private ConfigService configService;

	@Override
	public List<Event> listAllEvent() {

		return null;
	}

	public void initCookiesAndHeaders() {

		headers = prepareBasicHeaders();
		if(CollectionUtils.isNotEmpty(cookieDAO.findAll())){
			// re-store cookie for logined session or basic session for the first time
			if (cookies.getCookies() == null) {
				cookies.setCookies(cookieDAO.findAll());
			}
			headers.put(COOKIE, cookies.getCookie());
		} else {
			try {
				redirectNavigator(DEFAULT_URL);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public int exposeEvents(float bet_amount_per_time) {
		List<Event> events = null;
		try {
			initCookiesAndHeaders();
			// go to In-Play -> Soccer
			Document soccers = redirectNavigator(SOCCER_IN_PLAY_URL);
			Elements eEvents = soccers.select("div.Level2");
			events = eventService.exposeAllEvent(eEvents);
			// persist events
			events = eventService.persistEvent(events);
			LogUtil.logs(events);
			List<Contract> allContracts = contractService.exposeAllContract(events,	headers, cookies); 
			if(!CollectionUtils.isEmpty(allContracts)){
				List<Contract> qualifyContracts = aiService.getQualifyContract(allContracts); // filter
				if (qualifyContracts != null) {
					aiService.virtualPlaceBet(qualifyContracts, bet_amount_per_time);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return CollectionUtils.isEmpty(events) ? 0 : events.size();
	}

	@Override
	public void waitingResult() {
		try {
			List<SettledContract> listSettled = settledContractDAO.getListRunningSettledContract();
			if(CollectionUtils.isNotEmpty(listSettled)) {
				for(SettledContract running : listSettled){
					Event runningEvent = settledContractService.getRunningEvent(running);
					Document runningPage = null;

					try {
						runningPage = redirectNavigator(runningEvent.getLocation());

						if (runningPage.select(".Score").size() > 0 && runningPage.select(".Score") != null) {
							Element eScore = runningPage.select(".Score").get(0);
							// update running settled contract
							running.setCurrentGoal(CalcUtil.getTotalGoal(eScore.text()));
							runningEvent.setPlayTime(CalcUtil.getCurrentTime(runningPage.select("#ct0").get(0).text()));
							runningEvent.setTotalGoal(CalcUtil.getTotalGoal(eScore.text()));
							eventDAO.update(runningEvent);
							boolean dangerous = (runningEvent.getTotalGoal() + 1) >= CalcUtil.getUnderTotalGoal(running.getContract().getContractType());
							logger.info("Waiting event      :: " + runningEvent.getName());
							logger.info("Play time          :: " + runningEvent.getPlayTime());
							logger.info("Current goals      :: " + runningEvent.getTotalGoal());
							logger.info("Settled bet        :: " + running.getContract().getContractType());
							logger.info("Dangerous          :: " + dangerous);
							running.setDangerous(dangerous);
							running.setCurrentTime(runningEvent.getPlayTime());
							running.setHome_name(runningEvent.getHomeName());
							running.setAway_name(runningEvent.getAwayName());
							settledContractDAO.update(running);
							monitor.offer(running);
						} else {
							Element eFinish = null;
							boolean done = false;
							if (runningPage.select(".Level1").size() > 0 && runningEvent.getPlayTime() >= 90) { // match done, check money and continue bet
								finishedBet(running, runningEvent);
								done = true;
							}
							try {
								if(runningPage.select(".Level1").size() > 0){
									eFinish = runningPage.select(".Level1").get(0);
								}
								if (eFinish != null) {
									if (eFinish.text().indexOf("this page is no longer available") > -1 ) {
										if(!done){
											finishedBet(running, runningEvent);
										}
									}
								}
							} catch (Exception e) {
								logger.info("Match done error while parse: " + eFinish.text());
								e.printStackTrace();
							}
						}
					} catch (Exception e) {
						logger.info(" waitingResult -> redirectNavigator OR persist event error ");
						continue;
					}
				}
				logger.info("--try to confirm balance for missing finished bet");
				//try to confirm balance for missing finished bet
				listSettled = settledContractDAO.getListRunningSettledContract();

				//is there anything running?
				if(CollectionUtils.isNotEmpty(listSettled)){
					//wow, is there full slot.
					if(configService.get_bet_times() == listSettled.size()){
						logger.info("--slot full && nothing finished yet.");
					}else{
						logger.info("--another bet still running");
						Float balance = userAccountService.getRealBalance();
						if(balance > 1f){
							Float betWonAmount = 0f;
							for(SettledContract sc : listSettled){
								betWonAmount += sc.getBetAmount();
							}
							//balance greater than all running 0.5 ==> another won but not checked yet
							if(balance > (betWonAmount + betWonAmount * 0.5)){
								logger.info("--balance greater than all running 0.5 ==> another won but not checked yet");
								try{
									logger.info("sleeping about " + waitingTimes + "s for waiting result!!!!!");
									Thread.sleep(waitingTimes * 1000);
									waitingTimes += SLEEP_PER_TIMES;
								}catch(InterruptedException ie){}
								finally{
									waitingResult();
								}
							}
						}
						logger.info("--online balance: " + balance + " is ok!");
					}
				}else{
					logger.info("--finished all contract");
				}
				waitingTimes = SLEEP_PER_TIMES;
			}
		} catch (Exception e) {
			logger.info("waitingResult error");
			e.printStackTrace();
		}
	}

	@Override
	public String placeBet(Contract bestContract, Float amount) {
		try {
			logger.info("begin placebet");
			if(amount == 0f){
				return null;
			}

			if (bestContract != null) {
				logger.info("Add Bet Slip [" + bestContract.getId() + "]: " + bestContract.getBetSlipUrl());
				Document placeBetPage = redirectNavigator(bestContract.getBetSlipUrl());
				// check still valid
				if (placeBetStillValid(placeBetPage)) {
					logger.info("remove all other bet slips if existed");
					placeBetPage = removeAllBetSlips(placeBetPage, bestContract.getBetSlipUrl());
					if (placeBetPage != null) {

						if(contractIsValid(bestContract, placeBetPage)){
							if(appProperties.isTestMode()){
								try{
									Thread.sleep(CalcUtil.randomTime(5, 30) * 1000);
									return "TEST_MODE";
								}catch(Exception ex){}
							}

							// logger.info(placeBetPage);
							Map<String, String> formData = new HashMap<String, String>();
							formData.put("__VIEWSTATE", "");
							//formData.put("sn1", userAccountService.getRealBalance().toString());
							formData.put("sn1", amount.toString());
							formData.put("atbs", "Place Bet");
							// place bet action
							logger.info("postForm place bet timeout 30s !!!");
							Document betReceiptPage = postForm(PLACE_BET_SLIP, formData);
							// logger.info(betReceiptPage);
							logger.info("Check settled is successful or not");
							return settledSuccessful(betReceiptPage);
						}
					}
				}
			}
		} catch (Exception e) {
			logger.debug(ToStringBuilder.reflectionToString(e, ToStringStyle.SHORT_PREFIX_STYLE));
			e.printStackTrace();
		}
		logger.info("finished placebet");
		return null;
	}

	private boolean contractIsValid(Contract bestContract, Document placeBetPage) {
		boolean ret = true;

		try {
			//check bet rate
			Float betPageRate = getContractRate(placeBetPage);
			if(betPageRate == 0f){
				return false;
			}

			Float contractRate = bestContract.getRate();
			if(!betPageRate.equals(contractRate)){
				logger.info("Bet rate has changed [" + contractRate + "][" + betPageRate + "]: Denied ");
				if(betPageRate > contractRate){
					return false;
				}
			}

			//check contract type
			int totalPageGoal = getContractUnderTotal(placeBetPage);
			int contractGoal = CalcUtil.getUnderTotalGoal(bestContract.getContractType());
			if(totalPageGoal != contractGoal){
				logger.info("Bet contract has changed[" + bestContract.getContractType() + "]: Denied ");
				return false;
			}
		} catch (Exception e) {
			logger.debug(ToStringBuilder.reflectionToString(e, ToStringStyle.SHORT_PREFIX_STYLE));
			e.printStackTrace();
		}

		return ret;
	}

	private Float getContractRate(Document placeBetPage){
		try{
			Elements ele = placeBetPage.select(".normal");
			if(ele.size() == 1){
				String rate = ele.get(0).text();
				return Float.parseFloat(rate);
			}else{
				logger.info("Bet rate has changed");
				ele = placeBetPage.select("suspended");
				if(ele.size() > 1){
					String rate = ele.get(0).text();
					return Float.parseFloat(rate);
				}
			}
		}catch(Exception ex){
			logger.error(ex);
			ex.printStackTrace();
		}
		return 0f;
	}

	private int getContractUnderTotal(Document placeBetPage){
		int total = 0;
		try{
			Elements ele = placeBetPage.select(".header");
			if(ele.size() == 1){
				String str = ele.get(0).text();
				if(StringUtils.contains(str, "Under")){
					total = CalcUtil.getUnderTotalGoal(str);
				}
			}
		}catch(Exception ex){
			logger.error(ex);
			ex.printStackTrace();
		}
		return total;
	}

	private Document removeAllBetSlips(Document placeBetPage, String betSlipUrl) {

		Elements betSlip = placeBetPage.select(".BetSlip");
		logger.info("Number of bet slips : " + betSlip.size());
		if (betSlip != null && betSlip.size() > 1) {
			int removeTime = betSlip.size() - 1;
			for (int i = 0; i < removeTime; i++) {
				try {
					logger.info("Remove first bet slip until left one");
					placeBetPage = redirectNavigator(REMOVE_FIRST_BET_SLIP);
				} catch (Exception e) {
					logger.error(e);
					return null;
				}
			}
		}
		return placeBetPage;
	}

	private String settledSuccessful(Document betReceiptPage) {

		if (betReceiptPage.select(".oddsPanel") != null) {
			Element betRef = betReceiptPage.select(".oddsPanel").get(0);
			if (betRef != null && betRef.text().indexOf("To Return") > -1) {
				logger.info("Settled bet successful");
				try {
					logger.info("Total return : " + betRef.text());
				} catch (Exception e) {
					logger.error("just log not effect to main flow" + e);
				}
				return betRef.text();
			}
		}
		return null;
	}

	private boolean placeBetStillValid(Document placeBetPage) {

		try {
			String betslipHasChanged = placeBetPage.select(".menuRow").text();
			logger.info(betslipHasChanged);
			if (StringUtils.isNotEmpty(betslipHasChanged) && StringUtils.contains(betslipHasChanged, "betslip has changed")) {
				logger.info("placeBetStillValid has not valid any more - return false - change diff contract");
				return false;
			}
		} catch (Exception e) {
			logger.error(e);
			e.printStackTrace();
		}
		return true;
	}

	private void finishedBet(SettledContract running, Event runningEvent) {
		Boolean isWon = aiService.calcResult(running);
		running.setCurrentGoal(runningEvent.getTotalGoal());
		running.setWon(isWon);
		running.setRunning(false);

		//update db
		settledContractDAO.update(running);

		if(!appProperties.isTestMode()){
			settledContractService.updateOnlineStatus();
		}

		//update bet_current_count
		configService.set_bet_current_count(Manager.workingContract.size());

		//update MainThred bet watching
		Manager.match_done(running);

		//update monitor
		monitor.offer(running);

		logger.info(":::::::::::::::::::::::::::::::::::::");
		logger.info(":::::::::::::::::::::::::::::::::::::");
		logger.info("::::::::::" + runningEvent.getHomeName() + " v " + runningEvent.getAwayName() + "::::::::::::");
		logger.info("Event finised - Result : " + (isWon ? " Won " : " Lost"));
		logger.info("Event play time : " + runningEvent.getPlayTime());
		logger.info("Event current goals : " + runningEvent.getTotalGoal());
		logger.info(":::::::::::::::::::::::::::::::::::::");
		logger.info(":::::::::::::::::::::::::::::::::::::");
	}

}

